﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using OpenTK;
using OpenTK.Platform.Windows;
using OpenTK.Graphics.OpenGL;

using Toom.CellularAutomata.Model;
using Toom.CellularAutomata.Debug;
using Toom.CellularAutomata.Common;



namespace Toom.CellularAutomata.GUI
{
	public partial class FormField : Form
	{
		public FormField( FormCAMAIN formParent )
		{
			DebugPrintLine( CADebugger.DebugMessageSource.Default, "FormFieldDisplayer ctor" );

			InitializeComponent();

			this.parentForm = formParent;

			// setup event handlers for glControl
			glField.Load		+= this.glField_Load;
			glField.Paint		+= this.glField_Paint;
			glField.Resize		+= this.glField_Resize;
			glField.MouseDown	+= this.glField_MouseDown;
			glField.MouseUp		+= this.glField_MouseUp;
			glField.MouseMove	+= this.glField_MouseMove;

			// scrolling for field's panel
			panelField.VerticalScroll.Enabled = true;
			panelField.HorizontalScroll.Enabled = true;
			panelField.AutoScroll = true;
		}


		private FormCAMAIN parentForm;
		private Bitmap imgTitle;
		private CAField field = null;
		private bool glControlLoaded = false;


		public void SetField ( CAField f )
		{
			this.field = f;
		}

		#region OpenTK GLControl event handlers


		private void glField_Load(object sender, EventArgs e)
		{
			DebugPrintLine( CADebugger.DebugMessageSource.Painting, "Gr Load" );

			lock ( renderMutex )
			{
				// setup settings, load textures, sounds
				glField.VSync = true;	//VSyncMode.On

				GL.Viewport( 0, 0, glField.ClientSize.Width, glField.ClientSize.Height );

			}

			this.Width = panelField.ClientSize.Width;
			this.Height = panelField.ClientSize.Height;

			glControlLoaded = true;


		}

		private void glField_Resize(object sender, EventArgs e)
		{
			if (!glControlLoaded)
			{
				DebugPrintLine( CADebugger.DebugMessageSource.Painting, "Gr Resize skipped" );
				return;
			}

			DebugPrintLine( CADebugger.DebugMessageSource.Painting, "Gr Resize" );

			lock ( renderMutex )
			{
				GL.Viewport( 0, 0, glField.ClientSize.Width, glField.ClientSize.Height );
			}

			DebugPrintLine(CADebugger.DebugMessageSource.Painting, "glField_Resize new ClientSize=" + glField.ClientSize);

			//float aspect_ratio = Width / (float)Height;
			//Matrix4 perpective = Matrix4.CreatePerspectiveFieldOfView( MathHelper.PiOver4, aspect_ratio, 1, 64 );
			//GL.MatrixMode( MatrixMode.Projection );
			//GL.LoadMatrix( ref perpective );
		}

		private void glField_Paint(object sender, PaintEventArgs e)
		{
			if (!glControlLoaded)
			{
				DebugPrintLine( CADebugger.DebugMessageSource.Painting, "Gr Paint skipped" );
				return;
			}
			DebugPrintLine( CADebugger.DebugMessageSource.Painting, "Gr Paint" );

			// render graphics
			DisplayFieldAsImage(null);

		}//method

		#endregion


		#region GL Field mouse events for drawing

		private void glField_MouseDown( object sender, MouseEventArgs e )
		{
			if ( !CASettings.drawEnabled )
				return;

			DebugPrintLine( CADebugger.DebugMessageSource.Drawing, "MouseDown " + e.Location + " delta=" + e.Delta + " clicks=" + e.Clicks + " button=" + e.Button );

			int x = e.Location.X;
			int y = e.Location.Y;

			if ( x==oldPoint.X && y==oldPoint.Y )
			{
				DebugPrintLine( CADebugger.DebugMessageSource.Drawing, "MouseDown duplicate coords, exiting" );
				return;
			}

			// which color?
			int newData = CASettings.selectedColor;

			// draw point or line
			if ( CASettings.drawLines && oldPoint.X != -1 )
			{
				DrawLine( x, y, oldPoint.X, oldPoint.Y, newData );
			}
			else
			{
				DrawAt( x, y, newData );
			}

			// reset old point
			oldPoint.X = x;
			oldPoint.Y = y;
		}

		private void glField_MouseUp( object sender, MouseEventArgs e )
		{
			if ( !CASettings.drawEnabled )
				return;

			oldPoint.X = -1;
			oldPoint.Y = -1;
		}


		private Point oldPoint = new Point( -1, -1 );

		private void glField_MouseMove( object sender, MouseEventArgs e )
		{
			if ( !CASettings.drawEnabled )
				return;

			if ( e.Button == System.Windows.Forms.MouseButtons.None )
				return;
			DebugPrintLine( CADebugger.DebugMessageSource.Drawing, "MouseMove " + e.Location + " delta=" + e.Delta + " clicks=" + e.Clicks + " button=" + e.Button );

			int x = e.Location.X;
			int y = e.Location.Y;

			if ( x == oldPoint.X && y == oldPoint.Y )
			{
				DebugPrintLine( CADebugger.DebugMessageSource.Painting, "MouseDown duplicate coords, exiting" );
				return;
			}

			// which color?
			int newData = CASettings.selectedColor;

			// draw point or line
			if ( CASettings.drawLines )
			{
				DrawLine( x, y, oldPoint.X, oldPoint.Y, newData );
			}
			else
			{
				DrawAt( x, y, newData );
			}

			// reset old point
			oldPoint.X = x;
			oldPoint.Y = y;
		}


		private void DrawAt( int x, int y, int newVal )
		{
			if ( field == null )
			{
				DebugPrintLine( CADebugger.DebugMessageSource.Drawing, "DrawAt is exiting because field is null");
				return;
			}

			if ( x<0 || x>=field.sizeX || y<0 || y>=field.sizeY )
			{
				DebugPrintLine( CADebugger.DebugMessageSource.Default, "DrawAt exiting because corrds are invalid:" + x + "," + y );
				return;
			}

			//try
			//{
				field.SetDataAt( x, y, newVal );
				glField.Invalidate(true);
			//}
			//catch ( IndexOutOfRangeException iex )
			//{
			//	DebugPrintLine( CADebugger.DebugMessageSource.Default, "Problem assigning data to the model: field.SetDataAt() failed with IndexOutOfRangeException." );
			//}
		}

		private void DrawLine( int x1, int y1, int x2, int y2, int newVal )
		{
			int adx = Math.Abs( x1-x2 );
			int ady = Math.Abs( y1-y2 );
			float dy, dx;
			int nrStepsToDo;

			if ( ady > adx )
			{
				dx = ( (float)( x2-x1 )/( y2-y1 ) ) * Math.Sign( y2-y1 );
				dy = Math.Sign( y2-y1 );
				nrStepsToDo = ady+1;
			}
			else
			{
				dx = Math.Sign( x2-x1 );
				dy = ( (float)( y2-y1 )/( x2-x1 ) ) * Math.Sign( x2-x1 );
				nrStepsToDo = adx+1;
			}

			float x=x1;
			float y=y1;
			for ( int nrStepsSoFar=0; nrStepsSoFar<nrStepsToDo; nrStepsSoFar++  )
			{
				DrawAt( (int)x, (int)y, newVal );
				x+=dx;
				y+=dy;
			}
		}


		#endregion


		int countPaint=0;
		object renderMutex = new object();

		public void DisplayFieldAsImage( double? avgTPS )
		{
			if ( field == null )
			{
				DebugPrintLine( CADebugger.DebugMessageSource.Painting, "Gr DisplayFieldAsImage field=null -> exiting!" );
				lock ( renderMutex )
				{
					GLClear();
				}

				return;
			}
			DebugPrintLine( CADebugger.DebugMessageSource.Painting, "Gr DisplayFieldAsImage #"+countPaint );

			countPaint++;


			CATimer timer = new CATimer();

			// Display Title
			imgTitle = new Bitmap( pictureBoxFieldTitle.ClientSize.Width, pictureBoxFieldTitle.ClientSize.Height );
			Graphics g = Graphics.FromImage( imgTitle );
			Brush b = Brushes.Black;
			Font font = new Font( FontFamily.GenericMonospace, 13.0f, FontStyle.Regular );


			PointF point = new PointF( 0.0f, 0.0f );

			string title = "(" + String.Format( "{0:0.000}", avgTPS ) + " updates/s) " + field.GetTitle();
			g.DrawString( title, font, b, point );
			pictureBoxFieldTitle.Image = imgTitle;

			double time1 = timer.GetSAndReset();



			lock ( renderMutex )
			{
				// set GL control size according to field size
				glField.Width = field.sizeX;
				glField.Height = field.sizeY;

				// prep vars for displaying field
				float x0 = 0.0f;
				float xMax = glField.ClientSize.Width;
				float y0 = 0.0f;
				float yMax = glField.ClientSize.Height;

				float dx = ( xMax - x0 ) / field.sizeX;
				float dy = ( yMax - y0 ) / field.sizeY;

				// Display FIELD
				GLSetupViewport( x0, xMax, y0, yMax );
				GL.Clear( ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit );
				//GL.LineWidth( 3.0f );
				GL.PointSize( 1f );

				Color c;
				float x,y;

				for ( int dataY = 0 ; dataY < field.sizeY ; dataY++ )
				{
					for ( int dataX = 0 ; dataX < field.sizeX ; dataX++ )
					{
						int d = field.GetDataAt( dataX, dataY );

						// optimize
						if (d == 0)
							continue;

						c = CASettings.colors[d];

						x = x0   + (dataX * dx);
						y = yMax - (dataY * dy);

						//GL.Begin(PrimitiveType.Quads);
						//GL.Color3(c);

						//GL.Vertex2(x, y);
						//GL.Vertex2(x + dx, y);
						//GL.Vertex2(x + dx, y - dy);
						//GL.Vertex2(x, y - dy);

						//GL.End();


						GL.Begin( PrimitiveType.Points );
						GL.Color3( c );

						GL.Vertex2( x + dx/2, y + dy/2 );

						GL.End();
					}
				}

				try
				{
					glField.SwapBuffers();	// need to catch exceptions here. throws a lot.
				}
				catch ( OpenTK.Graphics.GraphicsContextException gcex )
				{
					CADebugger.DebugPrintLine( CADebugger.DebugMessageSource.Painting, gcex.Message );
					GLClear();
				}
			}//lock

			double time2 = timer.GetSAndReset();

			if ( !parentForm.runner.workerRunning )
			{
				//try
				//{
				DebugPrintLine( CADebugger.DebugMessageSource.Perf,
					"DisplayFieldAsImage " +
							"title " + time1.ToString( "0.000" ) +
							" field " + time2.ToString( "0.000" )
					);
				//}
				//catch(Exception)
				//{}
			}
			parentForm.perf.UpdateTracker( "drawTitle", time1 );
			parentForm.perf.UpdateTracker( "draw",		time2 );

			DebugPrintLine( CADebugger.DebugMessageSource.Painting, "DisplayFieldAsImage exiting happily" );

		}


		private void GLClear()
		{
			GL.Clear( ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit );
			glField.SwapBuffers();
		}

		private void GLSetupViewport(float x0, float xMax, float y0, float yMax)
		{
			GL.MatrixMode( MatrixMode.Projection );
			GL.LoadIdentity();
			GL.Ortho( x0, xMax, y0, yMax, 0.0, 4.0 );
		}

		#region Displaying Field as Text
		private void linkDisplayFieldAsText_LinkClicked( object sender, LinkLabelLinkClickedEventArgs e )
		{
			if ( field == null )
			{
				textboxField.Text = "Field was not yet created.  Please use one of the init functions first.";
				return;
			}

			DisplayFieldAsText( field.ToString() );
		}

		private void DisplayFieldAsText( string fieldAsString )
		{
			this.textboxField.Text = fieldAsString;
		}


		#endregion



		#region Debugging

		private void DebugPrintLine( CADebugger.DebugMessageSource src, string msg )
		{
			CADebugger.DebugPrintLine(src, msg);
		}

		#endregion




	}
}
